#ifndef __ROOM_H__
#define __ROOM_H__

#include"db.hpp"
#include"online.hpp"
#define BOARD_ROW 15
#define BOARD_COL 15

#define WHITE_CHESS 1
#define BLACK_CHESS 2

typedef enum {GAME_START,GAME_OVER}room_status;
class room 
{
public:
    room(uint64_t room_id,user_table *user_info,online_manager *online_user)
    :_room_id(room_id),_user_info(user_info),_online_user(online_user),
    _status(GAME_START),_white_id(0),_black_id(0),_player_count(0),
    _board(BOARD_ROW,std::vector<int>(BOARD_COL,0))
    {   
        DLOG("%lu房间创建成功",_room_id);
    }
    ~room()
    {
        DLOG("%lu房间关闭成功",_room_id);
    }
    /*处理下棋动作，处理发过来的下棋请求*/
    Json::Value handler_chess(Json::Value &req){
        Json::Value resp=req;//后面要返回的响应内容
        //1.判断请求房间号是否与当前房间的房间号匹配---已集成
        
        //2.判断是否双方都在线，任意一个不在线，另一方胜利
        int chess_row=req["row"].asInt();
        int chess_col=req["col"].asInt();
        uint64_t cur_uid=req["uid"].asInt64();
        if(_online_user->is_in_game_room(_white_id)==false){
            // resp["optype"]="put chess";//因为前面是复制了req，所以这里理应有内容了，不需要改这个，因为都一样
            // resp["room_id"]=_room_id;
            // resp["uid"]=cur_uid;//这有啥用？？？？
            // resp["row"]=chess_row;   
            // resp["col"]=chess_col;
            resp["result"]=true;
            resp["reason"]="对方掉线，你赢了";
            resp["winner"]=_black_id;
            //无须再自己手动更新数据库---已经集成
            return resp; 
        }
        if(_online_user->is_in_game_room(_black_id)==false){
            resp["result"]=true;
            resp["reason"]="对方掉线，你赢了";
            resp["winner"]=_white_id;
            return resp; 
        }
        //3.下棋，如果已经有棋子就不能下（位置被占用）
        if(_board[chess_row][chess_col]!=0){
            // resp["optype"]="put chess";
            resp["result"]=false;
            resp["reason"]="位置被占用，不能下棋";
            return resp;
        }
        int cur_color=cur_uid==_white_id?WHITE_CHESS:BLACK_CHESS;
        _board[chess_row][chess_col]=cur_color;
        //4.判断是否有玩家胜利(从当前放下棋子位置判断是否有五颗同样的棋子，横竖斜四个方向)
        uint64_t winner_id=winner_judge(chess_row,chess_col,cur_color);
        if(winner_id!=0){//但是还是要判断用来更新数据库用户信息---已集成
            resp["reason"]="你赢了";//产生了胜利者才需要更新reson数据
        }
        //可以无需判断，因为可以从winner_id是0还是真正的胜利者id判断是否胜利，到时候，优先判断一下winner就行，=0无人胜利
        resp["result"]=true;
        resp["winner"]=winner_id; 
        return resp;

    }
    /*处理聊天动作，处理发过来的下棋请求*/
    Json::Value handler_chat(Json::Value &req){
        //1.检测房间是否一致----已经集成到一个函数里
        Json::Value resp=req;//直接复制一份，就可以不用全都自己填
        //2.检测是否有敏感词
        std::string msg=req["message"].asString();
        size_t pos=msg.find("垃圾");
        if(pos!=std::string::npos){
            resp["result"]=false;
            resp["reason"]="消息包含敏感词，不能发送！";
            return resp;
        }

        //3.广播，设置响应信息，返回出去
        resp["result"]=true;
        return resp;
    }
    /*处理玩家退出房间动作*/
    void handler_eixt(uint64_t uid){
      //判断当前房间状态：如果是下棋中退出，则对方胜利，如果是棋局结束退出，就是正常退出
      Json::Value resp;
      if(_status==GAME_START){
        uint64_t winner_id=uid==_white_id?_black_id:_white_id;
        resp["optype"]="put_chess";
        resp["result"]=true;
        resp["reason"]="对方退出，你赢了";
        resp["room_id"]=_room_id;
        resp["uid"]=uid;//这有啥用？？？？
        resp["row"]=-1;   
        resp["col"]=-1;
        resp["winner"]=winner_id;
        uint64_t loser_id=winner_id==_white_id?_black_id:_white_id;
        _user_info->lose(loser_id);
        _user_info->win(winner_id);
        _status=GAME_OVER;
        boardcast(resp);
      }
      //房间中玩家数量--
      _player_count--;
      return;
    }
    /*总的请求处理函数，在函数内部，区分请求类型，根据不同的请求调用不同的处理函数，得到响应进行广播*/
    void handler_request(Json::Value &req){
        //1.反正都要对房间匹配进行校验，把他们提出来放这里
        Json::Value resp;
        uint64_t room_id=req["room_id"].asInt64();
        if(room_id!=_room_id){
            resp["optype"]=req["optype"].asString();
            resp["result"]=false;
            resp["reason"]="房间号不匹配";
            // boardcast(resp);
            // return;
            return boardcast(resp);
        }
        //2.根据不同的请求类型调用不同的处理函数
        if(req["optype"].asString()=="put_chess"){
            resp=handler_chess(req);
            if(resp["winner"].asInt64()!=0)//这个更新数据也集成到这里，就不用次次都判断了
            {   
                uint64_t winner_id=resp["winner"].asInt64();
                uint64_t loser_id=winner_id==_white_id?_black_id:_white_id;
                _user_info->lose(loser_id);
                _user_info->win(winner_id);
                _status=GAME_OVER;
            }

        }else if(req["optype"]=="chat"){
            resp=handler_chat(req);
        }
        else{//如果走这里说明有问题
            resp["optype"]=req["optype"].asString();
            resp["result"]=false;
            resp["reason"]="未知请求类型";
        }
        return boardcast(resp);

    }
    /*将指定的信息广播给房间中所有玩家*/
    void boardcast(Json::Value &resp){
        //1.将要响应的信息进行序列化，将Json::Value中的数据序列化为json格式字符串
        std::string body;
        json_util::serialize(resp,body);
        //2.获取房间中所有用户的通信连接,并发送响应信息
        wsserver_t::connection_ptr w_conn=_online_user->get_conn_from_room(_white_id);
        if(w_conn.get()!=nullptr)   w_conn->send(body);
        wsserver_t::connection_ptr b_conn=_online_user->get_conn_from_room(_black_id);
        if(b_conn.get()!=nullptr)   b_conn->send(body);
        //这里这样发信息，岂不是到时候大家收到的都一样？那如果白棋赢了黑棋输了，他们的reason不是应该不一样吗，就大家应该看到自己对应的界面？？？？
        //而且此时好像也没写对应的失败者的reason，比如说你输了之类的
        return;
        //此时的房间就只有两个用户，一个白棋用户一个黑棋用户，如果后续有什么观战的，可以用链表之类的数据结构管理起来，进行广播
    }
    

    //返回信息部分
    uint64_t room_id(){return _room_id;}
    room_status status(){return _status;}
    int player_count() {return _player_count;}
    void add_white_user(uint64_t uid){_white_id=uid;_player_count++;}
    void add_black_user(uint64_t uid){_black_id=uid;_player_count++;}
    uint64_t get_white_user(){return _white_id;}
    uint64_t get_black_user(){return _black_id;}
private:
    //专门设计一个接口用来判断是否五个
    /*row和col是下棋位置row_off和col_off是偏移量，也就是方向*/
    bool five(int row,int col,int row_off,int col_off,int color)
    {   
        int count=1;
        int search_row=row+row_off;
        int search_col=col+col_off;
        while(search_row>=0&&search_row<BOARD_ROW&&
        search_col>=0&&search_col<BOARD_COL&&
        _board[search_row][search_col]==color){//同色棋子++
            count++;
            search_row+=row_off;
            search_col+=col_off;
            DLOG("A：COUNT=%d",count);
        }

        search_row=row-row_off;
        search_col=col-col_off;
        while(search_row>=0&&search_row<BOARD_ROW&&
        search_col>=0&&search_col<BOARD_COL&&
        _board[search_row][search_col]==color){
            count++;
            search_row-=row_off;
            search_col-=col_off;
            DLOG("B：COUNT=%d",count);
        }
        return (count>=5);
            
    }
    int winner_judge(int row,int col,int color)
    {
        //返回胜利玩家的ID，否则返回0
        if( five(row,col,0,1,color)||
            five(row,col,1,0,color)||
            five(row,col,-1,1,color)||
            five(row,col,-1,-1,color)){
                return color==WHITE_CHESS?_white_id:_black_id;
            }

        return 0;
    }

private:
    uint64_t _room_id;
    room_status _status;//如果是START状态，双方有一方退出，另一方就胜利，如果是OVER状态就不需要去管
    int _player_count;
    uint64_t _white_id;
    uint64_t _black_id;
    user_table *_user_info;
    online_manager *_online_user;
    std::vector<std::vector<int>> _board;//和前端应该是一一对应的，前端需要画棋盘，画出来的格子应该和后台管理的是一致的

};

using room_ptr=std::shared_ptr<room>;
class room_manager
{
public:
    /*初始化房间ID计数器*///--需要传ut和om是因为要进行数据管理，而且，room类创建对象的时候也要用
    room_manager(user_table *ut,online_manager*om)
    :_tb_user(ut),_online_user(om),_roomid_allocate(1){
        DLOG("房间创建完成");
    }
    ~room_manager(){
        DLOG("房间关闭完成");
    }
    /*为两个用户创建房间，并返回房间的只能指针管理对象*/
    room_ptr room_create(uint64_t uid1,uint64_t uid2){
        //两个用户再游戏大厅中进行对战匹配，匹配成功后创建房间
        //1.校验两个用户是否都在游戏大厅中，只有都在才需要创建房间
        if(_online_user->is_in_game_hall(uid1)==false||_online_user->is_in_game_hall(uid2)==false){
            DLOG("用户数量不够，创建房间失败：%lu,%lu",uid1,uid2);
            return room_ptr();
        }
        //2.创建房间，将用户信息添加到房间中
        {   
            std::unique_lock<std::mutex> lock(_mutex);
            room_ptr rp(new room(_roomid_allocate,_tb_user,_online_user));
            rp->add_black_user(uid1);
            rp->add_white_user(uid2);
            //3.将房间信息管理起来
            _rooms.insert(std::make_pair(_roomid_allocate,rp));
            _users_rooms.insert(std::make_pair(uid1,_roomid_allocate));
            _users_rooms.insert(std::make_pair(uid2,_roomid_allocate));
            _roomid_allocate++;
            //4.返回房间信息
            return rp;
        }  

    }
    /*通过房间ID获取房间信息*/
    room_ptr get_room_by_roomid(uint64_t room_id){
        std::unique_lock<std::mutex> lock(_mutex);
        auto it=_rooms.find(room_id);
        if(it==_rooms.end()){
            return room_ptr();
        }
        return it->second;
    }
    /*通过用户ID获取房间信息*/
    room_ptr get_room_by_uid(uint64_t uid){
        std::unique_lock<std::mutex> lock(_mutex);
        auto it=_users_rooms.find(uid);
        if(it==_users_rooms.end()){
            return room_ptr();
        }
        //这里不可以直接调用上面的，因为这里有一把锁，上面又有一把锁，互斥锁不可重入，会死锁
        uint64_t roomid=it->second;
        auto it2=_rooms.find(roomid);
        if(it2==_rooms.end()){
            return room_ptr();
        }
        return it2->second;
    }
    /*通过房间ID销毁房间*/
    void remove_room(uint64_t room_id){
        //因为房间信息，是通过智能指针在_rooms中进行管理的，因此只要将shared_ptr从_rooms中移除
        //则shared_ptr计数器==0，外界有没有对房间信息进行操作保存，就释放了,所以直接移除就行
        //但是在移除房间之前，得先把房间里的用户给移除完
        
        //1.通过房间id获取房间信息
        room_ptr rp=get_room_by_roomid(room_id);
        if(rp.get()==nullptr){
            return ;
        }
        //2.通过房间信息，获取房间中所有用户id
        uint64_t uid1=rp->get_black_user();
        uint64_t uid2=rp->get_white_user();
        //3.移除房间管理的用户信息
        std::unique_lock<std::mutex> lock(_mutex);
        _users_rooms.erase(uid1);
        _users_rooms.erase(uid2);
        //4.移除房间管理信息
        _rooms.erase(room_id);
    }
    /*删除房间中指定用户，如果房间中没有用户，也销毁房间，用户连接断开时被调用*/
    void remove_room_user(uint64_t uid){
        room_ptr rp=get_room_by_uid(uid);
        if(rp.get()==nullptr){
            return ;
        }
        rp->handler_eixt(uid);
        //如果房间中没有用户了，就关闭房间
        if(rp->player_count()==0)   
            remove_room(rp->room_id());
        return;
    }

private:
    uint64_t _roomid_allocate;//房间号由1开始进行分配
    std::mutex _mutex;
    user_table * _tb_user;
    online_manager* _online_user;
    std::unordered_map<uint64_t,room_ptr> _rooms;
    std::unordered_map<uint64_t,uint64_t> _users_rooms;//通过用户找到房间

};

#endif